package com.swiftride.service.impl;

import cn.dev33.satoken.secure.BCrypt;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.swiftride.constants.Constants;
import com.swiftride.constants.GlobalExceptionCode;
import com.swiftride.exception.GlobalException;
import com.swiftride.mapper.LoginMapper;
import com.swiftride.mapper.PermissionMapper;
import com.swiftride.pojo.domain.Login;
import com.swiftride.pojo.dto.LoginDto;
import com.swiftride.pojo.dto.LoginInfoDto;
import com.swiftride.pojo.param.RegisterParam;
import com.swiftride.pojo.vo.LoginVo;
import com.swiftride.properties.AutoAdminProperties;
import com.swiftride.properties.SaTokenStpProperties;
import com.swiftride.service.ILoginService;
import com.swiftride.template.AppWeChatTemplate;
import com.swiftride.utils.AssertUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * 登录表 服务实现类
 * </p>
 *
 * @author wy
 * @since 2024-07-31
 */
@Service
public class LoginServiceImpl extends ServiceImpl<LoginMapper, Login> implements ILoginService {
    //属性
    @Autowired
    private SaTokenStpProperties saTokenStpProperties;

    @Autowired
    private AutoAdminProperties autoAdminProperties;

    //mapper
    @Autowired
    private PermissionMapper permissionMapper;

    //组件
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Autowired
    private AppWeChatTemplate appWeChatTemplate;

    @Override
    public Long register(RegisterParam registerParam) {
        Login login = new Login();
        login.init();
        BeanUtil.copyProperties(registerParam, login, Constants.Boolean.FALSE);
        super.save(login);
        return login.getId();
    }

    @Override
    public LoginVo wxLogin(String openidCode, Integer loginType) {
        //1.参数效验 使用jsr303完成
        //2.业务验证
        String openid = appWeChatTemplate.getOpenid(openidCode);
        Login login = super.getOne(new LambdaQueryWrapper<Login>().eq(Login::getOpenId, openid)
                .eq(Login::getType, loginType));
        AssertUtil.isNotNull(login, GlobalExceptionCode.PARAM_PHONE_NOT_EXIST);
        //3.业务实现
        return getLoginVo(login);
    }

    @Override
    public LoginVo login(LoginDto loginDto) {
        //1.参数校验 使用jsr303完成
        //2.业务验证
        //2.1验证账号及账号类型
        Login login = super.getOne(new LambdaQueryWrapper<Login>().eq(Login::getUsername, loginDto.getUsername())
                .eq(Login::getType, loginDto.getType()));
        AssertUtil.isNotNull(login, GlobalExceptionCode.PARAM_PHONE_NOT_EXIST);
        //2.2验证密码
        if (!BCrypt.checkpw(loginDto.getPassword(), login.getPassword())) {
            throw new GlobalException(GlobalExceptionCode.PASSWORD_NOT_CORRECT);
        }
        //3.业务实现
        return getLoginVo(login);
    }

    private LoginVo getLoginVo(Login login) {
        //3.业务实现
        //3.1 登录并获取token数据
        Long loginId = login.getId();
        StpUtil.login(loginId);
        String tokenName = StpUtil.getTokenName();
        String tokenValue = StpUtil.getTokenValue();
        //3.2将登录信息保存到redis中
        String loginKey = String.format(Constants.Redis.LOGIN_REDIS_KEY, loginId);
        LoginInfoDto loginInfoDto = BeanUtil.copyProperties(login, LoginInfoDto.class);
        redisTemplate.opsForValue().set(loginKey, loginInfoDto, StpUtil.getTokenTimeout(), TimeUnit.SECONDS);
        //3.3获取权限数据
        List<String> permissions = permissionMapper.selectPermissionByLoginId(loginId);
        //3.4将权限信息保存到redis
        String permissionsKey = String.format(saTokenStpProperties.getPermissionKey(), loginId);
        redisTemplate.opsForValue().set(permissionsKey, permissions, StpUtil.getTokenTimeout(), TimeUnit.SECONDS);
        //3.4返回登录信息视图
        return new LoginVo(tokenName, tokenValue, permissions, loginInfoDto);
    }

    @Override
    public void updatePassword(LoginDto loginDto) {
        //1.获取登录id并判断是登录账号是否存在
        long loginId = StpUtil.getLoginIdAsLong();
        Login login = super.getOne(new LambdaQueryWrapper<Login>().eq(Login::getId, loginId)
                .eq(Login::getType, loginDto.getType()));
        AssertUtil.isNotNull(login, GlobalExceptionCode.PARAM_PHONE_NOT_EXIST);
        //2.判断原本密码是否正确
        if (!BCrypt.checkpw(loginDto.getPassword(), login.getPassword())) {
            throw new GlobalException(GlobalExceptionCode.PASSWORD_NOT_CORRECT);
        }
        //3.加密修改密码
        login.setPassword(BCrypt.hashpw(loginDto.getNewPassword(), BCrypt.gensalt()));
        //4.更新登录表
        super.updateById(login);
    }

    @Override
    public void loginOut() {
        //1.删除redis中的数据
        String loginId = StpUtil.getLoginIdAsString();
        //删除登录信息
        String loginKey = String.format(Constants.Redis.LOGIN_REDIS_KEY, loginId);
        redisTemplate.delete(loginKey);
        //删除权限信息
        String key = String.format(saTokenStpProperties.getPermissionKey(), loginId);
        redisTemplate.delete(key);
        //@TODO 删除GEO数据
        //2.退出登录
        StpUtil.logout();
    }

    @Override
    public void addAdmin() {
        String admin = autoAdminProperties.getAdmin();
        Login login = super.getOne(new LambdaQueryWrapper<Login>().eq(Login::getUsername, admin)
                .eq(Login::getType, Constants.Login.TYPE_ADMIN)
                .eq(Login::getAdmin, Constants.Boolean.TURE));
        if (ObjectUtil.isNull(login)) {
            login = new Login();
            login.init();
            login.setUsername(admin);
            //密码加密后存储
            String password = autoAdminProperties.getPassword();
            String hashPw = BCrypt.hashpw(password, BCrypt.gensalt());
            login.setPassword(hashPw);
            login.setType(Constants.Login.TYPE_ADMIN);
            login.setAdmin(Constants.Boolean.TURE);
            login.setNickName(admin);
            login.setName(admin);
            save(login);
        }
    }
}
